package com.qfoud.edu.framwork.servlet;

import com.alibaba.fastjson.JSON;
import com.qfoud.edu.framwork.adviser.RequestAndResponseAdviser;
import com.qfoud.edu.framwork.annotation.EnableGlobleExeption;
import com.qfoud.edu.framwork.annotation.EnableRequestAndResponseAdviser;
import com.qfoud.edu.framwork.container.MappingContainer;
import com.qfoud.edu.framwork.container.ParameterStragyContainer;
import com.qfoud.edu.framwork.exception.HandlerExceptionResolve;
import com.qfoud.edu.framwork.exception.MvcFramworkException;
import com.qfoud.edu.framwork.interceptor.base.InterceptorChain;
import com.qfoud.edu.framwork.interceptor.impl.InterceptorChainImpl;
import com.qfoud.edu.framwork.pojo.*;
import com.qfoud.edu.framwork.stragy.base.ParameterTypeStragy;
import com.qfoud.edu.framwork.utils.JavaTypeUtils;
import com.qfoud.edu.framwork.utils.StringUtils;
import org.apache.commons.beanutils.BeanUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author xiaobobo
 * @title: DispatcherServlet
 * @projectName cd-java-2303-project-parent
 * @description: 这个里面要干的事情：
 *     1、取出请求的URI地址
 *     2、在我们的容器中去找beandefinition对象
 *     3、取出method
 *     4、采用method.invoke()方法去执行
 * @date 2023/5/8  11:01
 */
public class DispatcherServlet extends HttpServlet {

    //这个是全局异常的模板
    private HandlerExceptionResolve handlerExceptionResolve=null;

    //这个是前后置处理器的实例对象(多态)
    private RequestAndResponseAdviser requestAndResponseAdviser=null;

    //这里维护一个实例
    private InterceptorChain interceptorChain=InterceptorChainImpl.getInstance();

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        //接下来在这里进行初始化操作
        //这个方法是用来初始化我们的全局异常的实例的
        initHandlerExceptionResolve(config);
        //这里是实例化我们的这个前置和后置处理器的地方
        initRequestAndResponseAdviser(config);
    }

    /**
     * 这里是实例化我们的前置和后置处理器的地方
     * @param config
     */
    private void initRequestAndResponseAdviser(ServletConfig config) {
        //获取上下文的对象
        ServletContext servletContext = config.getServletContext();
        //找Config的class对象
        Class<?> configClass = getConfigClass(servletContext);
        EnableRequestAndResponseAdviser annotation = configClass.getAnnotation(EnableRequestAndResponseAdviser.class);
        if(null!=annotation){
            //说明程序员是设置了这个处理器的
            String value = annotation.value();
            if(StringUtils.isEmpty(value)){
               throw new MvcFramworkException("前后置处理器的处理类未设置....");
            }
            //程序执行到这里：说明设置了处理类
            Class<?> aClass=null;
            try {
                aClass = Class.forName(value);
            } catch (ClassNotFoundException e) {
                throw new MvcFramworkException("前后置处理器的处理类路径设置有误....");
            }
            //程序执行到这里说明这个类路径无误
            try {
                requestAndResponseAdviser = (RequestAndResponseAdviser) aClass.newInstance();
            } catch (Exception e) {
                throw new MvcFramworkException("前后置处理器的实例化出错....");
            }
        }
    }

    /**
     * 初始化全局异常模板
     * @param config
     */
    private void initHandlerExceptionResolve(ServletConfig config) {
        //获取上下文的对象
        ServletContext servletContext = config.getServletContext();
        //找Config的class对象
        Class<?> configClass = getConfigClass(servletContext);
        //接下来就可以判断是否有EnableGlobException这个注解
        EnableGlobleExeption enableGlobleExeption = configClass.getAnnotation(EnableGlobleExeption.class);
        if(null!=enableGlobleExeption){
            //说明有这样一个注解
            //获取值(全局异常实现类的全路径)
            String globleExceptionPath = enableGlobleExeption.value();
            if(StringUtils.isEmpty(globleExceptionPath)){
                throw new MvcFramworkException("全局异常的类路径未设置");
            }
            //程序执行到这里说明是对的
            Class<?> globleExceptionClass=null;
            try {
                globleExceptionClass = Class.forName(globleExceptionPath);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                throw new MvcFramworkException("全局异常类路径反射出错");
            }
            //程序执行到这里：说明反射无误
            try {
                handlerExceptionResolve = (HandlerExceptionResolve) globleExceptionClass.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
                throw new MvcFramworkException("全局异常程序实例化出错");
            }


        }
    }

    /**
     * 找配置文件的class对象
     * @param servletContext
     * @return
     */
    private Class<?> getConfigClass(ServletContext servletContext) {
        //第一步：找到配置文件
        String config = servletContext.getInitParameter("config");
        if(StringUtils.isEmpty(config)){
            throw new MvcFramworkException("web.xml中未配置config节点");
        }
        //程序执行到这里说明配置了节点
        //第二步：反射获取这个类的class对象
        Class<?> configClass=null;
        try {
            configClass = Class.forName(config);
        } catch (ClassNotFoundException e) {
            throw new MvcFramworkException("config中的类路径有误");
        }
        return configClass;
    }


    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //取出请求的URI地址  端口之后  ？ 号之前的那一节地址
        String requestURI = req.getRequestURI();
        //设置一下这个编码
        req.setCharacterEncoding("UTF-8");
        //在容器中去找 这个beandefinition对象
        BeanDefinition beanDefinition = MappingContainer.getMappingContainer().get(requestURI);
        //判断这个beandefinition对象是否为空
        if(null==beanDefinition){
            throw new MvcFramworkException("no mapping");
        }
        //接下来执行到这里：说明beandefinition不为空
        //取出这个类的实例信息
        Object instance = beanDefinition.getInstance();
        //取出method
        Method method = beanDefinition.getBeanMethodDefinition().getMethod();
        //接下来开始通过反射执行
        try {
            //这里去触发这个拦截器的调用
            interceptorChain.dealReq(req);
            //这里申明一个用来装参数的这样一个数组
            Object[] args=new Object[beanDefinition.getBeanMethodDefinition().getBeanParameterDefinitions().size()];
            //申明Model
            Model model = new Model();
            handlerParameters(req,beanDefinition,resp,model,args);
            Object invoke = method.invoke(instance, args);
            //接下来要处理返回的结果
            handlerReturnVal(req,resp,invoke,model,beanDefinition);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("反射执行目标方法出错....");
            if(null!=handlerExceptionResolve){
                //说明程序员配置了全局异常
                handlerExceptionResolve.handler(e.getCause(),req,resp);
            }
        }
    }

    /**
     * 处理参数
     * @param req
     * @param beanDefinition
     */
    private void handlerParameters(HttpServletRequest req, BeanDefinition beanDefinition,HttpServletResponse resp,Model model,Object...args) throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        //首先第一步：我们要获取参数集合
        List<BeanParameterDefinition> beanParameterDefinitions = beanDefinition.getBeanMethodDefinition().getBeanParameterDefinitions();
        // 如果集合是ArrayList的话 这个集合是实现了 RandomAccess这个接口的
        // RandomAccess 支持随机快速的访问  这个随机快速的访问 在fori的情况下是效率很高的
        // for each的底层是迭代器 迭代器本身在 LinkedList的效率比较高 所以这里最好使用 fori

        for (int i = 0; i <beanParameterDefinitions.size() ; i++) {
            BeanParameterDefinition beanParameterDefinition = beanParameterDefinitions.get(i);
            //接下来就要处理单个的参数了...
            handlerSingleParameter(beanParameterDefinition,req,i,resp,model,args);
        }
    }
    /**
     * 处理单个参数
     * @param beanParameterDefinition
     * @param req
     */
    private void handlerSingleParameter(BeanParameterDefinition beanParameterDefinition, HttpServletRequest req,int index,HttpServletResponse resp,Model model,Object... args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, IOException {
        //首先要获取参数的类型
        // Java中的单个数据类型
        // HttpServlerRequest
        // HttpServletResponse
        // 自定义类的类型
        // JSON格式转换的算一类
        //第一步：获取类型
        String parameterType = beanParameterDefinition.getParameterType();
        System.out.println("参数类型是:"+parameterType);
        String parameterName = beanParameterDefinition.getParameterName();
        //判断是不是Java中的单个数据类型

        String type="";
        if(jugmentTypeIsJavaOrNot(parameterType)){
            type="javaSingleType";
        }else{
            type=parameterType;
        }
        ParameterTypeStragy parameterTypeStragy = ParameterStragyContainer.getStragyMap().get(type);
        if(null!=parameterTypeStragy){
            //说明是上面的类型
            parameterTypeStragy.run(model,resp,beanParameterDefinition,req,index,parameterType,parameterName,args);
        }else {
            //如果不是Java类型的参数 那么就是对象类型
            objectTypeParameterSet(beanParameterDefinition,req,index,parameterType,parameterName,args);
        }

//        if(jugmentTypeIsJavaOrNot(parameterType)){
//            //这里就是处理Java类型的参数设置
//            javaTypeParameterSet(req, index, parameterType, parameterName, args);
//        }else if(StringUtils.equals(parameterType,"javax.servlet.http.HttpServletRequest")){
//            //说明需要的参数是request
//            args[index]=req;
//        }else if(StringUtils.equals(parameterType,"javax.servlet.http.HttpServletResponse")){
//            //说明需要的参数是response
//            args[index]=resp;
//        }else if(StringUtils.equals(parameterType, "com.qfoud.edu.framwork.pojo.Model")){
//            //说明传递了一个Model值
//            args[index]=model;
//        }else if(StringUtils.equals("java.lang.String[]",parameterType)){
//            //说明需要的是一个String类型的数组来接受参数的
//            stringArrayTypeParameterSet(req, index, parameterType, parameterName, args);
//        }else{
//            //如果不是Java类型的参数 那么就是对象类型
//            objectTypeParameterSet(beanParameterDefinition,req,index,parameterType,parameterName,args);
//        }

    }



    /**
     * 对象类型的参数设置
     * @param req
     * @param index
     * @param parameterType
     * @param parameterName
     * @param args
     */
    private void objectTypeParameterSet(BeanParameterDefinition beanParameterDefinition,HttpServletRequest req, int index, String parameterType, String parameterName, Object[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException, IOException {

        //这里有两种情况
        // 第一种情况：前端传JSON ->Java
        // 第二种情况：前端传键值对->Java对象
        Boolean requestBodyOrNot = beanParameterDefinition.getRequestBodyOrNot();
        if(requestBodyOrNot){
            //说明前端传递的是JSON格式
            // 注意：HTTP协议传递JSON的时候 数据只能放到 请求实体中 请求实体中的数据: 流
            // 复习:get -->getParameter()   getQueryString()
            // 复习:post --> 键值对的时候 getParameter()   getInputStream();
            String str=getReqData(req);
            //TODO 触发点原来在这里(requestBody之前可以解密)
            if(null!=requestAndResponseAdviser){
                str=requestAndResponseAdviser.before(str);
            }
            //将这个类型弄成class对象
            Class<?> aClass = Class.forName(parameterType);
            //接下来就要将这个值转换成对象？
            Object object = JSON.parseObject(str, aClass);
            args[index]=object;
            return ;
        }
        //第一步：获取这个类型的class对象
        Class<?> clazz = Class.forName(parameterType);
        //实例化对象
        Object object = clazz.newInstance();
        //要将req中的参数设置给 args
        BeanUtils.populate(object,req.getParameterMap());
        //将这个对象设置给参数
        args[index]=object;
    }

    /**
     * 获取请求数据(从请求中获取)
     * @param req
     * @return
     */
    private String getReqData(HttpServletRequest req) throws IOException {
        ServletInputStream in = req.getInputStream();
        byte[] buf=new byte[8192];
        int readLength = in.read(buf);
        //将获取的数据进行转换
        return new String(buf,0,readLength,"UTF-8");
    }


    /**
     * 判断数据类型是不是Java中的单个数据类型
     * @param parameterType
     * @return
     */
    private boolean jugmentTypeIsJavaOrNot(String parameterType) {
        String[] javaType = JavaTypeUtils.getJavaType();
        for (int i = 0; i <javaType.length ; i++) {
            String type=javaType[i];
            if(StringUtils.equals(type,parameterType)){
               return true;
            }
        }
        return false;
    }

    /**
     * 处理返回结果
     * @param req
     * @param resp
     * @param invoke
     */
    private void handlerReturnVal(HttpServletRequest req, HttpServletResponse resp, Object invoke,Model model,BeanDefinition beanDefinition) throws IOException, ServletException {
        //说明返回的是其他的数据
        //获取方法的描述
        BeanMethodDefinition beanMethodDefinition = beanDefinition.getBeanMethodDefinition();
        Boolean responseBodyOrNot = beanMethodDefinition.getResponseBodyOrNot();
        if(responseBodyOrNot){
            //执行到这里：说明这个鬼需要转换成JSON格式将数据写回去
            sendResponse(resp,invoke);
            return;
        }else if(invoke instanceof String){
            handlerReturnStringType(req, resp, invoke,model);
            return;
        }else if(invoke instanceof ModelAndView){
            //说明返回了一个ModelAndView对象
            handleModelAndViewType(req, resp, invoke);
            return ;
        }else{
            if(!responseBodyOrNot){
                throw new MvcFramworkException("非法的返回值");
            }
        }
    }

    /**
     * 将数据以JSON的格式直接写回去
     * @param resp
     * @param invoke
     */
    private void sendResponse(HttpServletResponse resp, Object invoke) {
        //第一步：将invoke转换成JSON格式
        String jsonString = JSON.toJSONString(invoke);
        //TODO：前置和后置处理器的触发点
        if(null!=requestAndResponseAdviser){
            jsonString= requestAndResponseAdviser.after(jsonString);
        }
        //第二步：将数据写回到我们的页面中
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = resp.getWriter();
            writer.write(jsonString);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理返回ModelAndView对象的
     * @param req
     * @param resp
     * @param invoke
     */
    private void handleModelAndViewType(HttpServletRequest req, HttpServletResponse resp, Object invoke) throws ServletException, IOException {
        //先将返回值弄成ModelAndView这个值
        ModelAndView modelAndView= (ModelAndView) invoke;
        //将值取出来
        setDataToReq(req,modelAndView);
        //接下来取出这个要跳转的页面
        String viewName = modelAndView.getViewName();
        //TODO 跳转页面
        jumpPage(viewName,req,resp);
    }

    /**
     * 处理咋们的String类型的返回
     * @param req
     * @param resp
     * @param invoke
     * @throws IOException
     * @throws ServletException
     */
    private void handlerReturnStringType(HttpServletRequest req, HttpServletResponse resp, Object invoke,Model model) throws IOException, ServletException {
        //说明需要进行页面的跳转
        String path= String.valueOf(invoke);
        //接下来判断这个地址是否以 redirect:
        if(path.startsWith("redirect:")){
            //这里还需要将地址前面的redirect给去掉
            path=path.replace("redirect:","").trim();
            //如果成立说明是重定向
            resp.sendRedirect(path);
            return ;
        }
        //执行到这里才需要考虑这个Model中数据的问题 因为是转发
        if(model.getMaps().size()>0){
            //说明有数据放到了域对象中
            //接下来就要将数据取出来 最终放到req中去
            setDataToReq(req,model);
        }
        jumpPage(path,req,resp);
        return ;
    }

    /**
     * 页面跳转
     * @param path
     * @param req
     * @param resp
     */
    private void jumpPage(String path, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //执行到这里说明是转发
        req.getRequestDispatcher(path).forward(req,resp);
    }

    /**
     * 这里就是将Model中的数据放到req中去
     * @param req
     * @param model
     */
    private void setDataToReq(HttpServletRequest req, Model model) {
        //获取迭代器
        Iterator<Map.Entry<String, Object>> it = model.getMaps().entrySet().iterator();
        while (it.hasNext()){
            Map.Entry<String, Object> next = it.next();
            //下面获取放到Model中的键值对
            String key = next.getKey();
            Object value = next.getValue();
            req.setAttribute(key,value);
        }

    }
}
